package com.briup.day20;

import lombok.Getter;

/**
 * 编写程序，实现以下生产者-消费者问题：
 * 1. 有多个生产者(Productor)每间隔1秒将生产的一个产品交给店员(Clerk)
 * 2. 有多个消费者(Customer)每间隔1秒可以从店员处购买一个产品
 * 3. 一个店员最多能管理20个产品
 * 4. 如果店员管理的产品数量超过20个，店员会叫生产者停止生产
 * 5. 如果店员管理的产品数量低于20个，再通知生产者继续生产
 * 6. 如果店员中没有产品了，店员会告诉消费者等一下，当有产品时，通知消费者购买产品
 */
public class ThreadTest {
    public static void main(String[] args) {
        // 创建3个生产者，3个消费者，1个店员
        Clerk clerk = new Clerk();
        for (int i = 1; i <= 2; i++) {
            Productor productor = new Productor(clerk, "生产者" + i);
            productor.start();
            Consumer consumer = new Consumer(clerk, "消费者" + i);
            consumer.start();
        }
        Consumer consumer = new Consumer(clerk, "消费者3");
        consumer.start();
    }
}

@Getter
class Clerk {
    // 店员维护产品数量
    private int productNum;

    // 通过店员操控产品的数量：生产产品和消费产品
    // 生产产品
    public synchronized void product() throws InterruptedException {
        String name = Thread.currentThread().getName();
        if (productNum >= 20) {
            System.out.printf("当前产品滞销，请%s先休息片刻%n", name);
            // 调用wait()方法，让当前执行任务的线程进入到无限等待状态，注意需要被其他线程唤醒
            // wait()方法是Object类中定义的方法，因为该方法是锁对象调用的方法，
            // 即wait()方法只能在同步代码中调用，并且只能是锁对象调用
            wait();
        }
        // 实际生产产品
        System.out.printf("%s当前正在生产第%s个产品，目前有%s个产品%n",
                name, ++productNum, productNum);
        // 在生产完产品后，也得唤醒因为缺少产品而休眠的消费者
        notify();
        Thread.sleep(1000);
    }

    // 消费产品
    public synchronized void consume() throws InterruptedException {
        String name = Thread.currentThread().getName();
        if (productNum == 0) {
            System.out.printf("当前产品已售罄，请[%s]明日再来%n", name);
            // 休眠
            wait();
        }
        System.out.printf("%s当前正在消费第%s个产品，目前有%s个产品%n",
                name, productNum, --productNum);
        // 当消费完产品时，记得告知生产者继续生产产品，即唤醒处于无限休眠状态的线程
        // 在所有因为本锁对象调用wait()方法而进入到无线休眠状态的线程中，随机选中一个线程被唤醒
        // notify()也是定义在Object类中的方法
        // notifyAll()是唤醒所有因为本锁对象调用wait()方法而进入到无线休眠状态的线程中
        notify();
        Thread.sleep(1000);
    }
}

// 创建生产者线程类
class Productor extends Thread {
    private final Clerk clerk;

    // 绑定维护店员与生产者之间的联系
    public Productor(Clerk clerk, String name) {
        super(name);
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            try {
                clerk.product();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Consumer extends Thread {
    private final Clerk clerk;

    // 绑定维护店员与消费者之间的联系
    public Consumer(Clerk clerk, String name) {
        super(name);
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            try {
                clerk.consume();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}